package com.fuyun.rpc.client.fault.tolerant;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 简单熔断器实现
 */
@Slf4j
public class CircuitBreaker {

	// 熔断器状态
	public enum State {
		CLOSED,     // 关闭状态
		OPEN,       // 打开状态
		HALF_OPEN   // 半开状态
	}

	// 配置参数
	private final int failureThreshold;        // 失败阈值
	private final long timeout;               // 超时时间(毫秒)
	private final int successThreshold;       // 半开状态下成功阈值

	// 状态变量
	private volatile State state = State.CLOSED;
	private final AtomicInteger failureCount = new AtomicInteger(0);
	private final AtomicInteger successCount = new AtomicInteger(0);
	private final AtomicLong lastFailureTime = new AtomicLong(0);

	public CircuitBreaker(int failureThreshold, long timeout, int successThreshold) {
		this.failureThreshold = failureThreshold;
		this.timeout = timeout;
		this.successThreshold = successThreshold;
	}

	/**
	 * 检查是否允许执行请求
	 * @return 是否允许执行
	 */
	public boolean canExecute() {
		switch (state) {
			case CLOSED:
				return true;

			case OPEN:
				// 检查是否应该切换到半开状态
				if (System.currentTimeMillis() - lastFailureTime.get() >= timeout) {
					state = State.HALF_OPEN;
					successCount.set(0);
					log.info("熔断器切换到半开状态");
					return true;
				}
				return false;

			case HALF_OPEN:
				return true;

			default:
				return true;
		}
	}

	/**
	 * 记录成功事件
	 */
	public void onSuccess() {
		switch (state) {
			case CLOSED:
				failureCount.set(0);
				break;

			case OPEN:
				break;

			case HALF_OPEN:
				if (successCount.incrementAndGet() >= successThreshold) {
					// 成功次数达到阈值，关闭熔断器
					state = State.CLOSED;
					failureCount.set(0);
					log.info("熔断器关闭");
				}
				break;
		}
	}

	/**
	 * 记录失败事件
	 */
	public void onFailure() {
		switch (state) {
			case CLOSED:
				if (failureCount.incrementAndGet() >= failureThreshold) {
					// 失败次数达到阈值，打开熔断器
					state = State.OPEN;
					lastFailureTime.set(System.currentTimeMillis());
					log.info("熔断器打开");
				}
				break;

			case OPEN:
				// 在打开状态下仍然记录失败时间
				lastFailureTime.set(System.currentTimeMillis());
				break;

			case HALF_OPEN:
				// 半开状态下失败，重新打开熔断器
				state = State.OPEN;
				lastFailureTime.set(System.currentTimeMillis());
				log.info("熔断器重新打开");
				break;
		}
	}

	/**
	 * 获取当前状态
	 * @return 当前状态
	 */
	public State getState() {
		return state;
	}
}